package com.lan.extend.dict.scanner;

import com.lan.extend.dict.aspectj.LanDictData;
import com.lan.extend.dict.aspectj.LanDictParam;
import com.lan.extend.dict.config.LanDictProperties;
import com.lan.extend.dict.domain.LanDictType;
import com.lan.extend.dict.utils.ClassUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.*;
import java.util.*;

@Component
public class LanDictAnnotationScanner {

    private static final Logger log = LogManager.getLogger(LanDictAnnotationScanner.class);

    /**
     * 字典所依赖的查询方法所在的类名
     */
    private static final Map<String, Object> lanBeanMap = new HashMap<>();

    /**
     * 字典所依赖的查询方法名
     */
    private static final Map<String, Map<String, Object>> lanKeyMap = new HashMap<>();

    /**
     * 字典所依赖的查询方法的返回值
     */
    private static final Map<String, String> lanReturnTypeMap = new HashMap<>();

    /**
     * 构建返回对象的属性的信息
     */
    private static final Map<String, Map<String, Map<String, String>>> lanObjectKeyInListMap = new HashMap<>();

    private static final String METHOD_NAME = "methodName";

    private static final String REFRESH_TIME = "refreshTime";

    private static final String ENABLED = "enabled";

    @Autowired
    private ApplicationContext context;

    @Autowired
    private LanDictProperties lanDictProperties;

    public LanDictAnnotationScanner() {}

    /**
     * 初始化全部数据
     */
    public void init() {
        if (lanBeanMap.isEmpty() || lanKeyMap.isEmpty() || lanReturnTypeMap.isEmpty() || lanObjectKeyInListMap.isEmpty()) {
            clear();
            if (lanDictProperties.getScanPackage() == null) {
                log.error("请指定字典扫描位置！");
            } else {
                scanPackageForAnnotation(lanDictProperties.getScanPackage());
//                log.info(lanBeanMap.keySet());
            }
        }
    }

    /**
     * 清除全部缓存
     */
    private void clear() {
        lanBeanMap.clear();
        lanKeyMap.clear();
        lanReturnTypeMap.clear();
        lanObjectKeyInListMap.clear();
    }

    /**
     * 扫描对象下的类，用以初始化数据
     *
     * @param packageName 包的根路径
     */
    private void scanPackageForAnnotation(String packageName) {
        // 获取包路径下的所有类名
        List<String> classNames = ClassUtil.getClasses(packageName);
//        log.info(classNames);
        // 遍历类名并检查注解
        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);

                Method[] declaredMethods = clazz.getDeclaredMethods();
                for (Method method : declaredMethods) {
                    // 检查类的方法是否包含指定注解
                    if (method.isAnnotationPresent(LanDictData.class)) {
                        analysisLanDictData(clazz, method);
                    }
                }

                Field[] declaredFields = clazz.getDeclaredFields();
                for (Field field : declaredFields) {
                    // 检查类的属性是否包含指定注解
                    if (field.isAnnotationPresent(LanDictParam.class)) {
                        analysisLanDictParam(className, field);
                    }
                }
            } catch (ClassNotFoundException e) {
                log.error(e);
            }
        }
    }

    /**
     * 解析方法上的LanDictData注解
     * @param clazz 需要解析的类
     * @param method 需要解析的方法
     */
    private void analysisLanDictData(Class<?> clazz, Method method) {
        LanDictData annotation = method.getAnnotation(LanDictData.class);
        Map<String, Object> map = new HashMap<>();
        map.put(METHOD_NAME, method.getName());
        map.put(REFRESH_TIME, annotation.refreshTime());
        map.put(ENABLED, annotation.enabled());
        lanKeyMap.put(annotation.value(), map);
        Object bean = null;
        try {
            bean = context.getBean(clazz);
        } catch (Exception e) {
            try {
                Constructor<?> constructor = clazz.getConstructor();
                bean = constructor.newInstance();
            } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
                throw new RuntimeException(ex);
            }
        }
        lanBeanMap.put(annotation.value(), bean);

        String returnType = ClassUtil.getReturnType(method);
        lanReturnTypeMap.put(annotation.value(), returnType);
    }

    /**
     * 解析方法上的LanDictParam注解
     * @param className 需要解析的类名
     * @param field 需要解析的属性
     */
    private void analysisLanDictParam(String className, Field field) {
        Map<String, Map<String, String>> objectKeys = lanObjectKeyInListMap.get(className);
        if (objectKeys == null) objectKeys = new HashMap<>();
        LanDictParam annotation = field.getAnnotation(LanDictParam.class);
        Map<String, String> keyDetails = new HashMap<>();
        keyDetails.put("key", getKey(annotation, field));
        objectKeys.put(field.getName(), keyDetails);
        lanObjectKeyInListMap.put(className, objectKeys);
    }

    /**
     * 根据注解LanDictParam和对象属性判断键名（key）是属性名还是指定名
     *
     * @param annotation
     * @param field
     * @return
     */
    private String getKey(LanDictParam annotation, Field field) {
        String key = "";
        //设置了指定值
        if ("".equals(annotation.key())) key = field.getName();
        //是唯一值
        if ("".equals(annotation.key()) && annotation.type().equals(LanDictType.VALUE)) key = "value";
        //是标签名
        if ("".equals(annotation.key()) && annotation.type().equals(LanDictType.LABEL)) key = "label";
        return key;
    }

    /**
     * LanDictData注解的方法的对象
     * @param key 字典键名
     * @return
     */
    public Object getBean(String key) {
        Object bean = lanBeanMap.get(key);
        if (bean == null) isExistsLanDictData(key);
        return bean;
    }

    /**
     * LanDictData注解的方法
     * @param key 字典键名
     * @return
     */
    public Method getMethod(String key, Object bean) throws NoSuchMethodException {
        Map<String, Object> keyMap = lanKeyMap.get(key);
        Method method = bean.getClass().getMethod((String) keyMap.get(METHOD_NAME));
        method.setAccessible(true);
        return method;
    }

    /**
     * LanDictParam注解的属性信息
     * @param key 字典键名
     * @return
     */
    public Map<String, Map<String, String>> getFieldList(String key) throws NoSuchMethodException {
        String returnType = lanReturnTypeMap.get(key);
        Map<String, Map<String, String>> keyMap = lanObjectKeyInListMap.get(returnType);
        if (keyMap == null) isExistsLanDictParam(returnType);
        return keyMap;
    }

    /**
     * LanDictData注解是否开启了缓存
     * @param key 字典键名
     * @return
     */
    public boolean enabled(String key) {
        Map<String, Object> keyMap = lanKeyMap.get(key);
        if(keyMap == null){
            isExistsLanDictData(key);
            return false;
        }
        return (boolean) keyMap.get(ENABLED);
    }

    /**
     * LanDictData注解是否设置了缓存时间
     * @param key 字典键名
     * @return
     */
    public long getRefreshTime(String key) {
        Map<String, Object> keyMap = lanKeyMap.get(key);
        if(keyMap == null) {
            isExistsLanDictData(key);
            return 0;
        }
        return (Long) keyMap.get(REFRESH_TIME);
    }

    private void isExistsLanDictData(String key){
        log.error("{}不存在对应接口！请添加@LanDictData(\"{}\")", key, key);
    }

    private void isExistsLanDictParam(String returnType){
        log.error("{}不存在@LanDictParam注解的属性", returnType);
    }
}
